package org.bing.engine.common.graniteds;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.granite.config.GraniteConfig;
import org.granite.config.flex.ServicesConfig;
import org.granite.context.AMFContextImpl;
import org.granite.context.GraniteContext;
import org.granite.logging.Logger;
import org.granite.messaging.amf.AMF0Message;
import org.granite.messaging.amf.io.AMF0Deserializer;
import org.granite.messaging.amf.io.AMF0Serializer;
import org.granite.messaging.webapp.AMFMessageFilter;
import org.granite.messaging.webapp.AMFMessageServlet;
import org.granite.messaging.webapp.HttpGraniteContext;
import org.granite.util.ServletParams;

public class GraniteAmfServlet extends AMFMessageServlet {
    private static final long serialVersionUID = 1978;
    private static final Logger log = Logger.getLogger(AMFMessageFilter.class);

    private GraniteConfig gdsGrtCfg = null;
    private ServicesConfig gdsSrvCfg = null;
    private boolean closeStreams = true;

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        InputStream cins = this.getResourceStream("graniteConfigLocation", "/META-INF/flex-granite-config.xml");

        try {
            this.gdsGrtCfg = new GraniteConfig(null, cins, null, null);
        } catch (Exception e) {
            log.error("Fail to init granite config.", e);
        } finally {
            close(cins);
        }

        InputStream sins = this.getResourceStream("serviceConfigLocation", "/META-INF/flex-granite-service.xml");
        try {
            this.gdsSrvCfg = new ServicesConfig(sins, null, false);
        } catch (Exception e) {
            log.error("Fail to init granite service.", e);
        } finally {
            close(sins);
        }
        closeStreams = ServletParams.get(config, "closeStreams", Boolean.TYPE, true);
        log.info("Init granite servlet complete.");
    }

    @Override
    public void destroy() {
        super.destroy();
        this.gdsSrvCfg = null;
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        DataInputStream is = null;
        DataOutputStream os = null;
        try {
            GraniteContext context = HttpGraniteContext.createThreadIntance(gdsGrtCfg, gdsSrvCfg, this.getServletContext(), request, response);
            AMFContextImpl amf = (AMFContextImpl) context.getAMFContext();
            log.debug(">> Deserializing AMF0 request...");

            AMF0Deserializer deserializer = new AMF0Deserializer(new DataInputStream(new BufferedInputStream(request.getInputStream())));
            AMF0Message amf0Request = deserializer.getAMFMessage();
            amf.setAmf0Request(amf0Request);
            log.debug(">> Chaining AMF0 request: %s", amf0Request);

            process(request, response);

            AMF0Message amf0Response = amf.getAmf0Response();
            log.debug("<< Serializing AMF0 response: %s", amf0Response);

            response.setStatus(HttpServletResponse.SC_OK);
            response.setContentType(AMF0Message.CONTENT_TYPE);
            response.setDateHeader("Expire", 0L);
            response.setHeader("Cache-Control", "no-store");

            os = new DataOutputStream(new BufferedOutputStream(response.getOutputStream()));
            AMF0Serializer serializer = new AMF0Serializer(os);
            serializer.serializeMessage(amf0Response);

            os.flush();
            response.flushBuffer();
        } catch (Exception e) {
            log.error(e, "AMF message error!");
            throw new ServletException(e);
        } finally {
            if (closeStreams) {
                close(is);
                close(os);
            }
            GraniteContext.release();
        }
    }

    private void process(HttpServletRequest request, HttpServletResponse response) throws ServletException {
        try {
            GraniteContext context = GraniteContext.getCurrentInstance();
            if (context == null) {
                throw new ServletException("Can't find GraniteContext from context!");
            }
            AMFContextImpl amf = (AMFContextImpl) context.getAMFContext();

            AMF0Message amf0Request = amf.getAMF0Request();

            log.debug(">> Processing AMF0 request: %s", amf0Request);

            AMF0Message amf0Response = AMF0MessageProcessor.process(amf0Request);

            log.debug("<< Returning AMF0 response: %s", amf0Response);

            amf.setAmf0Response(amf0Response);
        } catch (Exception e) {
            log.error(e, "AMF message error");
            throw new ServletException(e);
        }
    }

    private InputStream getResourceStream(String name, String def) {
        String location = this.getServletContext().getInitParameter(name);
        if (location == null) {
            location = def;
        }
        InputStream ins = null;
        if (location.startsWith("classpath")) {
            location = location.substring(location.indexOf(":") + 1);
            ins = GraniteAmfServlet.class.getResourceAsStream(location);
        } else {
            ins = this.getServletContext().getResourceAsStream(location);
        }
        return ins;
    }

    private void close(Closeable cls) {
        try {
            if (cls != null) {
                cls.close();
            }
        } catch (IOException e) {
            log.error("Fail to close stream!", e);
            cls = null;
        }
    }
}
